## Not Clever Particle Enginer
## Copyright (c) 2008 Casey Duncan and Harry Tormey
## See LICENSE.txt for licensing details MIT License
## http://en.wikipedia.org/wiki/MIT_License

import pyglet
import random, math
from pyglet import image
from pyglet.gl import *
import ctypes


#float	xi;// X Direction or velocity
#float	yi;// Y Direction
#float	zi;// Z Direction
#float	xg;// X Gravity
#float	yg;// Y Gravity
#float	zg;// Z Gravity
#bool	active;// Active (Yes/No)
#float	life;// Particle Life
#float	fade;// Fade Speed
#float	r;// Red Value
#float	g;// Green Value
#float	b;// Blue Value
class Particle(ctypes.Structure):
	"""Simple"""
	_fields_ = [
		('x', ctypes.c_float),
		('y', ctypes.c_float),
		('z', ctypes.c_float),
		('xi', ctypes.c_float),
		('yi', ctypes.c_float),
		('zi', ctypes.c_float),
		('xg', ctypes.c_float),
		('yg', ctypes.c_float),
		('zg', ctypes.c_float),
		('r', ctypes.c_float),
		('g', ctypes.c_float),
		('b', ctypes.c_float),
		('active', ctypes.c_int),
		('life', ctypes.c_float),
		('fade', ctypes.c_float),
	]

class Vec3(ctypes.Structure):
	"""Simple"""
	_fields_ = [
		('x', ctypes.c_float),
		('y', ctypes.c_float),
		('z', ctypes.c_float),
	]

def lerp_pos((startx, starty, startz), (endx, endy, endz), count):
	"""Linear interpolation count positions between start and end"""
	dx = float(endx - startx) / count
	dy = float(endy - starty) / count
	dz = float(endz - startz) / count
	for i in xrange(int(count)):
		yield (startx, starty, startz)
		startx += dx
		starty += dy
		startz += dz


#Over ride me to produce different types of particle effects, like missile collision
class ParticleEmitter():
	def __init__(self, particleSystem, position, rate, particleVelocityDeviation = (0,0,0), 
			particleBaseVelocity = (0,0,0),
			particleLifespan = 1):
		'''
		particleSystem = system to use
		position = where its gonna appear, x,y,z
		rate = particles per time slice
		'''
		self.particleSystem = particleSystem
		self.position = self.lastPosition = position
		self.rate = rate
		self.particleVelocityDeviation = [float(x) for x in particleVelocityDeviation]
		self.particleBaseVelocity = [float(x) for x in particleVelocityDeviation]
		self.particleLifespan = particleLifespan

	def update(self, timeDelta):
		'''
		timeDelta pyglet supplys this, random period of time since last update
		'''
		# Figure out how many particles to emit
		count = self.rate * timeDelta 
		# Account for fractional emission
		count += random.random() < (count - int(count))

		for pos in lerp_pos(self.lastPosition, self.position, count):
			self.emit(pos)

		self.lastPosition = self.position
		
	#for subclass overide emit and makes it do different things
	def emit(self, position):
		vx, vy, vz = self.particleBaseVelocity
		dvx, dvy, dvz = self.particleVelocityDeviation
		velocity = (random.gauss(vx, dvx), random.gauss(vy, dvy), random.gauss(vz, dvz))
		self.particleSystem.addParticle(position, velocity, self.particleLifespan)

#Over ride me to produce different types of particle effects, like missile collision
class ParticleTextureEmitter(ParticleEmitter):
	def __init__(self, particleTextureSystem, position, rate, particleVelocityDeviation = (0,0,0), 
			particleBaseVelocity = (0,0,0),
			particleLifespan = 1):
		'''
		particleSystem = system to use
		position = where its gonna appear, x,y,z
		rate = particles per time slice
		'''
		self.colors = ((1.0,0.5,0.5),
			(1.0,0.75,0.5),
			(1.0,1.0,0.5),
			(0.75,1.0,0.5),
			(0.5,1.0,0.5),
			(0.5,1.0,0.75),
			(0.5,1.0,1.0),
			(0.5,0.75,1.0),
			(0.5,0.5,1.0),
			(0.75,0.5,1.0),
			(1.0,0.5,1.0),
			(91.0,0.5,0.75))
		self.particleSystem = particleTextureSystem
		self.position = self.lastPosition = [float(x) for x in position]
		self.rate = rate
		self.particleVelocityDeviation = [float(x) for x in particleVelocityDeviation]
		self.particleBaseVelocity = [float(x) for x in particleVelocityDeviation]
		self.particleLifespan = particleLifespan

	#for subclass overide emit and makes it do different things
	def emit(self, position):
		vx, vy, vz = self.particleBaseVelocity
		dvx, dvy, dvz = self.particleVelocityDeviation
		x, y, z = position
		velocity = (random.gauss(vx, dvx), random.gauss(vy, dvy), random.gauss(vz, dvz))
		vx, vy, vz = velocity
		particle = (x,y,z, #position
				vx,vy,vz, #velocity
				0,0,0,    #gravity
				self.colors[random.randint(0,11)][0],self.colors[random.randint(0,11)][1],self.colors[random.randint(0,11)][2],    #red, green, blue
				1, self.particleLifespan,float((random.randint(1,100)/1000.0)+0.1), #Active 1 = true, life, fade
			)
		self.particleSystem.addParticle(particle)

class ParticleTextureSystem:
	def __init__(self, maxParticles, gravityVector = None):
		#Create an array of particle thats MaxParticles in size, what you get back is a class you have to call
		self.particles =(Particle * maxParticles)()
		#create list of empty entries so index doesn't mismatch
		self.particleEndTime  = [None]*maxParticles
		self.maxParticles = maxParticles
		self.time = 0
		self.texture=None
		#index of last particle
		self.particleIndex = 0
		#use this to detect killed particles, time to live expired, etc
		self.offScreen = -1000000
		#None evaluates to false, if true then perform for loop, wont evaluate right hand side if false.
		self.gravityVector = gravityVector and [ float(x) for x in gravityVector]
		#Hack alert, I copied this in from the textureemitter
		self.colors = ((1.0,0.5,0.5),
			(1.0,0.75,0.5),
			(1.0,1.0,0.5),
			(0.75,1.0,0.5),
			(0.5,1.0,0.5),
			(0.5,1.0,0.75),
			(0.5,1.0,1.0),
			(0.5,0.75,1.0),
			(0.5,0.5,1.0),
			(0.75,0.5,1.0),
			(1.0,0.5,1.0),
			(91.0,0.5,0.75))
	def addParticle(self, particle):
		'''3 member instances of floats'''
		self.particles [self.particleIndex] = Particle(*particle)
		if self.particles[self.particleIndex].life is not 0:
			self.particleEndTime[self.particleIndex] = (self.time + self.particles[self.particleIndex].life)
		else: 
			self.particleEndTime[self.particleIndex] = 0
		self.particleIndex += 1
		if self.particleIndex >= self.maxParticles:
			self.particleIndex = 0
	def update(self, timeDelta):
		'''timeDetla = seconds/frames since last called,
		loop through all the particles and update their positions 
		based on their velocities. Add more later (color etc)'''
		#note to self, xrange usually faster than range, range creates list
		self.time += timeDelta
		for i in xrange(self.maxParticles):
			self.particles[i].life -= self.particles[i].fade
			if self.particleEndTime[i] > self.time:
				self.particles[i].x += self.particles[i].xi * timeDelta
				self.particles[i].y += self.particles[i].yi * timeDelta
				self.particles[i].z += self.particles[i].zi * timeDelta
				if self.gravityVector is not None:
					self.particles[i].xi += self.gravityVector[0] *timeDelta
					self.particles[i].yi += self.gravityVector[1] *timeDelta
					self.particles[i].zi += self.gravityVector[2] *timeDelta
			elif self.particles[i].z != self.offScreen:
				self.killParticle(i)
			if (self.particles[i].life <= 0.0):#If Particle Is Burned Out
				self.particles[i].life=1.0#Give It New Life
				self.particles[i].fade=float((random.randint(1,100)/1000.0)+0.003)#Random Fade Value
				self.particles[i].x=0.0#Center On X Axis
				self.particles[i].y=0.0#Center On Y Axis
				self.particles[i].z=0.0#Center On Z Axis
				self.particles[i].r=self.colors[random.randint(0,11)][0]#Select Red From Color Table
				self.particles[i].g=self.colors[random.randint(0,11)][1]#Select Green From Color Table
				self.particles[i].b=self.colors[random.randint(0,11)][2]#Select Blue From Color Table
	def loadParticleTexture(self):
		pic = image.load('Particle.bmp')
		width, height = pic.width, pic.height
		self.texture = pic.texture
		glTexParameteri(self.texture.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
 		glTexParameteri(self.texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
	def draw(self):
		scaletext = 2.5
		#set up opengl to draw for loop of textures
		glBindTexture(self.texture.target, self.texture.id)
		for i in xrange(self.maxParticles):
			#// Draw The Particle Using Our RGB Values, Fade The Particle Based On It's Life
			glColor4f(self.particles[i].r,self.particles[i].g,self.particles[i].b,self.particles[i].life);
			glBegin(GL_TRIANGLE_STRIP)#Build Quad From A Triangle Strip
			glTexCoord2d(1,1)
			glVertex3f(self.particles[i].x+scaletext,self.particles[i].y+scaletext,self.particles[i].z)#Top Right
			glTexCoord2d(0,1)
			glVertex3f(self.particles[i].x-scaletext,self.particles[i].y+scaletext,self.particles[i].z)#Top Left
			glTexCoord2d(1,0)
			glVertex3f(self.particles[i].x+scaletext,self.particles[i].y-scaletext,self.particles[i].z)#Bottom Right
			glTexCoord2d(0,0)
			glVertex3f(self.particles[i].x-scaletext,self.particles[i].y-scaletext,self.particles[i].z)#Bottom Left
			glEnd()
	def killParticle(self, index):
		'''So this is a hack to not display the particle @ index. 
		at present we set coordinates to draw off screen, we may want to change this later.
		-could set color to background
		-size to 0
		-texture to background,
		-replace shader, skip
		-position it off screen
		'''
		self.particles[index].z = self.offScreen

if __name__ == '__main__':


	win = pyglet.window.Window(resizable=True, visible = False)


	def resize(widthWindow, heightWindow):
		glClearColor( 0, 0, 0, 0 )
		glViewport( 0, 0, widthWindow, heightWindow)
		glMatrixMode (GL_PROJECTION)
		glLoadIdentity ()
		glOrtho (0, widthWindow, heightWindow, 0, -1.0, 1)
		glMatrixMode (GL_MODELVIEW)
		glLoadIdentity()
		#glDisable(GL_DEPTH_TEST);							// Disable Depth Testing
		glEnable(GL_BLEND)
		glShadeModel(GL_SMOOTH)# Enables Smooth Shading
		#glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
		glBlendFunc(GL_SRC_ALPHA,GL_ONE)#Type Of Blending To Perform
		glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);#Really Nice Perspective Calculations
		glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);#Really Nice Point Smoothing
		glDisable(GL_DEPTH_TEST)
		glEnable (GL_TEXTURE_2D)

	win.resize = resize
	win.set_visible(True)
	win.resize(win.width, win.height)
	numParticles = 500
	particles = ParticleTextureSystem(numParticles, (0, -50, 0))
	particles.loadParticleTexture()
	emitter = ParticleTextureEmitter(particleTextureSystem = particles,  
		position = (300,450, 0), 
		rate=400, 
		particleVelocityDeviation = (5,8,0), 
		particleBaseVelocity =(0,0,0),
		particleLifespan=2)
	def move_emitter(timeDelta):
		emitter.position = (
			(math.sin(particles.time) * 250) + 300, 
			(math.cos(particles.time) * 200) + 250, 0)	
	move_emitter(0)

#	for i in range(numParticles):
#		particles.addParticle(position = (300,300,0), velocity = (random.gauss(0,20), random.gauss(0,20), 0))
	#once every that perioed, call update with this timeDelta.
	pyglet.clock.schedule_interval(emitter.update, (1.0/30.0))
	pyglet.clock.schedule_interval(particles.update, (1.0/30.0))
	#win.draw = particles.drawSystem
	#Note read up on decorators and overriding events in python

	pyglet.clock.schedule_interval(move_emitter, (1.0/30.0))
	@win.event
	def on_draw():
		win.clear()
		glLoadIdentity()
		particles.draw()
	pyglet.app.run()

#    win.clear()
    #ParticleGlPoint()
#    text.draw()
#    win.flip()
